// --------------------------------------------------------------------------------------------------------------------
// <copyright file="OrderTracker.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge
{
    #region Usings

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;

    using Org.OpenTrader.Framework.Forge.Enums;
    using Org.OpenTrader.Framework.Forge.Events;
    using Org.OpenTrader.Framework.Forge.Interfaces;

    #endregion

    /// <summary>
    /// The order tracker.
    /// </summary>
    public class OrderTracker : IOrderTracker
    {
        #region Constants and Fields

        /// <summary>
        /// The identity.
        /// </summary>
        private static readonly Identity Identity = Identity.Create(MethodBase.GetCurrentMethod());

        /// <summary>
        /// The OrderProvider.
        /// </summary>
        private readonly IOrderProvider OrderProvider;

        /// <summary>
        /// The trackingCells.
        /// </summary>
        private readonly IDictionary<Guid, IOrderTrackingCell> trackingCells = new Dictionary<Guid, IOrderTrackingCell>();

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="OrderTracker"/> class.
        /// An order tracker can track orders sent thouth an IInstrument
        /// </summary>
        /// <param name="instrument">
        /// The instrument.
        /// </param>
        /// <param name="orderProvider">
        /// The orderProvider.
        /// </param>
        public OrderTracker(IInstrument instrument, IOrderProvider orderProvider)
        {
            this.EventCB = new RemotableEventHandler<OrderTrackingEventArgs>("OrderTracker");
            this.OrderProvider = orderProvider;
            this.Instrument = instrument;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets EventCB.
        /// </summary>
        public RemotableEventHandler<OrderTrackingEventArgs> EventCB { get; set; }

        /// <summary>
        /// Gets Instrument.
        /// </summary>
        public IInstrument Instrument { get; private set; }

        #endregion

        #region Indexers

        /// <summary>
        /// Return the tracking cell associated to some order (or null)
        /// </summary>
        /// <param name="orderId"></param>
        /// <returns></returns>
        public IOrderTrackingCell this[Guid orderId]
        {
            get
            {
                if (this.trackingCells.ContainsKey(orderId))
                {
                    return this.trackingCells[orderId];
                }

                return null;
            }
        }

        #endregion

        #region Implemented Interfaces

        #region IOrderTracker

        /// <summary>
        /// Cancel some order, but before, update needed fields in tracking cell
        /// </summary>
        /// <param name="order">
        /// </param>
        /// <returns>
        /// The cancel order.
        /// </returns>
        public Status CancelOrder(IOrder order)
        {
            // Invalid order
            if (order == null)
            {
                return Status.Failure(Identity, "CancelOrder: Invalid Order");
            }

            // Not tracked here
            if (!this.trackingCells.ContainsKey(order.ID))
            {
                return Status.Failure(Identity, "CancelOrder: This order is not tracked here");
            }

            var cell = this.trackingCells[order.ID] as OrderTrackingCell;

            if (cell.Pending)
            {
                cell.NextStatus = ENextOrderStatus.Cancelling;
                return Status.Failure(Identity, "CancelOrder: This order is pending cannot cancel, will be cancelled automaticaly");
            }

            // Reset next status
            if (cell.NextStatus == ENextOrderStatus.Cancelling)
            {
                cell.NextStatus = ENextOrderStatus.NULL;
            }

            // Update cell
            cell.CancelStamp = Timings.Now;
            var result = this.OrderProvider.CancelOrder(order);
            cell.Cancelling = result;

            // Raise appropriate event (the cell has been modified)
            this.RaiseSynchronousOrderTrackingEvent(order.ID, cell);

            // Operation result
            return result;
        }

        /// <summary>
        /// Create some order, and add the tracking structure
        /// </summary>
        /// <param name="type">
        /// </param>
        /// <param name="way">
        /// </param>
        /// <param name="price">
        /// </param>
        /// <param name="quantity">
        /// </param>
        /// <param name="bookingPath">
        /// </param>
        /// <returns>
        /// </returns>
        public Status<IOrder> CreateOrder(EOrderType type, EOrderWay way, double price, int quantity, string bookingPath)
        {
            // Ask the order service to create the order
            var createstatus = this.OrderProvider.CreateOrder(this.Instrument.InstrumentStaticData.ID, type, way, price, quantity, bookingPath, this.OnOrder);

            // Success -> add a cell to track this order
            if (createstatus)
            {
                var order = createstatus.NestedObject;
                var cell = new OrderTrackingCell(order, this);
                this.trackingCells[order.ID] = cell;

                // Raise appropriate event (the cell has been modified)
                this.RaiseSynchronousOrderTrackingEvent(order.ID, cell);
            }

            // Return to caller
            return createstatus;
        }

        /// <summary>
        /// The get tracking Cells conforming to given predicate.
        /// </summary>
        /// <param name="predicate">
        /// The predicate.
        /// </param>
        /// <returns>
        /// A table containing matching tracking Cells (may be empty)
        /// </returns>
        public IOrderTrackingCell[] GetTrackingCells(Expression<Func<IOrderTrackingCell, bool>> predicate)
        {
            var instruments = this.trackingCells.Values.AsQueryable().Where(predicate.Compile());
            return instruments.ToArray();
        }

        /// <summary>
        /// Modify the order, but before, update needed fields in tracking cell
        /// </summary>
        /// <param name="oldOrder">
        /// The oldOrder.
        /// </param>
        /// <param name="newOrder">
        /// The newOrder.
        /// </param>
        /// <returns>
        /// The modify order.
        /// </returns>
        public Status ModifyOrder(IOrder oldOrder, IOrder newOrder)
        {
            // Invalid Old Order
            if (oldOrder == null)
            {
                return Status.Failure(Identity, "ModifyOrder: Invalid Old Order");
            }

            // Invalid New Order
            if (newOrder == null)
            {
                return Status.Failure(Identity, "ModifyOrder: Invalid New Order");
            }

            // Not tracked here
            if (!this.trackingCells.ContainsKey(oldOrder.ID))
            {
                return Status.Failure(Identity, "ModifyOrder: This order is not tracked here");
            }

            // Update cell
            var cell = this.trackingCells[oldOrder.ID] as OrderTrackingCell;

            if (cell.Pending)
            {
                cell.NextStatus = ENextOrderStatus.Modifying;
                cell.NextOrder = newOrder;
                return Status.Failure(Identity, "ModifyOrder: This order is pending cannot modifyied,, will be modifyied automaticaly");
            }

            // Reset next status
            if (cell.NextStatus == ENextOrderStatus.Modifying)
            {
                cell.NextStatus = ENextOrderStatus.NULL;
                cell.NextOrder = null;
            }

            cell.PendingQuantity = newOrder.Quantity;
            cell.PendingPrice = newOrder.Price;
            cell.ModifyStamp = Timings.Now;
            var result = this.OrderProvider.ModifyOrder(oldOrder, newOrder);
            cell.Modifying = result;
            if (!cell.Modifying)
            {
                cell.PendingQuantity = 0;
                cell.PendingPrice = 0.0;
            }

            // Raise appropriate event (the cell has been modified)
            this.RaiseSynchronousOrderTrackingEvent(oldOrder.ID, cell);

            // Modify now
            return result;
        }

        /// <summary>
        /// Send some order, but before, update needed fields in tracking cell
        /// </summary>
        /// <param name="order">
        /// </param>
        /// <returns>
        /// The send order.
        /// </returns>
        public Status SendOrder(IOrder order)
        {
            // Invalid order
            if (order == null)
            {
                return Status.Failure(Identity, "SendOrder: Invalid Order");
            }

            // Not tracked here
            if (!this.trackingCells.ContainsKey(order.ID))
            {
                return Status.Failure(Identity, "SendOrder: This order is not tracked here");
            }

            // Update cell
            var cell = this.trackingCells[order.ID] as OrderTrackingCell;

            cell.PendingQuantity = order.Quantity;
            cell.PendingPrice = order.Price;
            cell.SendStamp = Timings.Now;

            // Send now
            var result = this.OrderProvider.SendOrder(order);
            cell.Sent = result;
            if (!result)
            {
                cell.PendingQuantity = 0;
                cell.PendingPrice = 0.0;
            }

            // Raise appropriate event (the cell has been modified)
            this.RaiseSynchronousOrderTrackingEvent(order.ID, cell);

            return result;
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// The check next status.
        /// </summary>
        /// <param name="trackingCell">
        /// The tracking cell.
        /// </param>
        /// <returns>
        /// The check next status.
        /// </returns>
        private bool CheckNextStatus(IOrderTrackingCell trackingCell)
        {
            // Check the trackingCell
            if (trackingCell == null)
            {
                return false;
            }

            // Check the Instrument
            if (this.Instrument == null)
            {
                return false;
            }

            // Check The Status
            if (trackingCell.NextStatus == ENextOrderStatus.Cancelling)
            {
                // Cancelling
                if (trackingCell.Open)
                {
                    // send cancel request
                    return this.Instrument.Cancel(trackingCell.Order.ID);
                }
            }
            else if (trackingCell.NextStatus == ENextOrderStatus.Modifying)
            {
                // Modifying
                if (trackingCell.Open)
                {
                    if (trackingCell.NextOrder != null)
                    {
                        // send modify request
                        return this.Instrument.Modify(
                            trackingCell.Order.ID, trackingCell.NextOrder.Price, trackingCell.NextOrder.Quantity, trackingCell.NextOrder.BookingPath);
                    }
                    else
                    {
                        return false;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Update tracking cell of order with events coming from market
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="args">
        /// </param>
        private void OnOrder(object sender, OrderEventArgs args)
        {
            // The tracking cell related to order we are receiving events for
            var cell = this.trackingCells[args.ObjectID] as OrderTrackingCell;
            if (cell == null)
            {
                return;
            }

            // Status (Market)
            if (args is OrderEventArgs.Status)
            {
                var sargs = args as OrderEventArgs.Status;

                if (sargs.OrderStatus == EOrderStatus.AckOrder)
                {
                    // New Order accepted
                    cell.Acknowledged = true;
                    cell.PendingQuantity = 0;
                    cell.PendingPrice = 0.0;
                    cell.RequestedQuantity = sargs.RequestedQuantity;
                    cell.RequestedPrice = sargs.RequestedPrice;
                    cell.RemainingQuantity = sargs.RemainingQuantity;
                    cell.AckStamp = Timings.Now;

                    // Check Next Status
                    this.CheckNextStatus(cell);
                }
                else if (sargs.OrderStatus == EOrderStatus.AckCancel)
                {
                    // Order Cancel accepted
                    cell.Cancelled = true;
                    cell.Cancelling = false;
                    cell.CancelQuantity = sargs.CancelQuantity;
                    cell.CancelAckStamp = Timings.Now;
                }
                else if (sargs.OrderStatus == EOrderStatus.AckModify)
                {
                    // Order Modify accepted
                    cell.Modified = true;
                    cell.Modifying = false;
                    cell.PendingQuantity = 0;
                    cell.PendingPrice = 0.0;
                    cell.RequestedQuantity = sargs.RequestedQuantity;
                    cell.RequestedPrice = sargs.RequestedPrice;
                    cell.RemainingQuantity = sargs.RemainingQuantity;
                    cell.ModifyAckStamp = Timings.Now;

                    // Check Next Status
                    this.CheckNextStatus(cell);
                }
                else if (sargs.OrderStatus == EOrderStatus.RejOrder)
                {
                    // Order was rejected
                    cell.Rejected = true;
                    cell.PendingQuantity = 0;
                    cell.PendingPrice = 0.0;
                    cell.CancelQuantity = sargs.CancelQuantity;
                    cell.RequestedQuantity = sargs.RequestedQuantity;
                    cell.RequestedPrice = sargs.RequestedPrice;
                    cell.RemainingQuantity = sargs.RemainingQuantity;
                    cell.RejectStamp = Timings.Now;
                    cell.RejectReason = sargs.RejectReason;
                }
                else if (sargs.OrderStatus == EOrderStatus.RejCancel)
                {
                    // Cancel was rejected
                    cell.Cancelling = false;
                    cell.CancelRejected++;
                    cell.RejectReason = sargs.RejectReason;

                    // Check Next Status
                    this.CheckNextStatus(cell);
                }
                else if (sargs.OrderStatus == EOrderStatus.RejModify)
                {
                    // Modify was rejected
                    cell.Modifying = false;
                    cell.ModifyRejected++;
                    cell.RejectReason = sargs.RejectReason;

                    // Check Next Status
                    this.CheckNextStatus(cell);
                }
                else if (sargs.OrderStatus == EOrderStatus.OutReport)
                {
                    cell.OutReport = true;
                    cell.OutReportStamp = Timings.Now;
                }
            }
            else if (args is OrderEventArgs.Execution)
            {
                var eargs = args as OrderEventArgs.Execution;

                // Add execution
                cell.AddExecution(new Execution(args.ObjectID, eargs.Time, eargs.Price, eargs.Quantity, eargs.Counterparty));

                // Update exec qty/avg price
                var qty = cell.ExecQuantity + eargs.Quantity;
                cell.ExecAveragePrice = (qty != 0) ? ((cell.ExecAveragePrice * cell.ExecQuantity) + (eargs.Price * eargs.Quantity)) / qty : 0.0;
                cell.ExecPrice = eargs.Price;
                cell.ExecQuantity = qty;
                cell.RemainingQuantity = eargs.RemainingQuantity;
                cell.PartExecuted = eargs.RemainingQuantity != 0;
                cell.Executed = eargs.RemainingQuantity == 0;

                // Check Next Status
                this.CheckNextStatus(cell);
            }
            else
            {
                return;
            }

            // Raise appropriate event (the cell has been modified)
            this.RaiseAsynchronousOrderTrackingEvent(args.ObjectID, cell, args);
        }

        /// <summary>
        /// The fire asynchronous order tracking event.
        /// </summary>
        /// <param name="objectID">
        /// The object id.
        /// </param>
        /// <param name="trackingCell">
        /// The tracking cell.
        /// </param>
        /// <param name="relatedOrderEventArgs">
        /// The related Order Event ForwardToSubscriberArgs.
        /// </param>
        private void RaiseAsynchronousOrderTrackingEvent(Guid objectID, IOrderTrackingCell trackingCell, OrderEventArgs relatedOrderEventArgs)
        {
            // Raise the event
            this.EventCB.RaiseEvent(this, new OrderTrackingEventArgs.AsynchronousUpdate(objectID, trackingCell, relatedOrderEventArgs));
        }

        /// <summary>
        /// The fire order tracking event.
        /// </summary>
        /// <param name="objectID">
        /// The object id.
        /// </param>
        /// <param name="trackingCell">
        /// The tracking cell.
        /// </param>
        private void RaiseSynchronousOrderTrackingEvent(Guid objectID, IOrderTrackingCell trackingCell)
        {
            // Raise the event
            this.EventCB.RaiseEvent(this, new OrderTrackingEventArgs.SynchronousUpdate(objectID, trackingCell));
        }

        #endregion
    }
}